<?php
namespace app\libs;

use yii;

class HttpRequest
{
    protected $hasCurl;
    protected $useCurl;
    protected $useBasicAuth;
    protected $HTTPVersion;
    protected $referer;
    protected $url;
    protected $host;
    protected $port;
    protected $uri;
    protected $type;
    protected $query;
    protected $timeout;
    protected $error;
    protected $response;
    protected $responseText;
    protected $responseHeaders;
    protected $executed;
    protected $fsock;
    protected $curl;
    protected $additionalCurlOpts;
    protected $authUsername;
    protected $authPassword;

    public function __construct($host = null, $uri = '/', $type = 'GET', $port = 80, $timeout = 40, $useCurl = null)
    {
        $this->hasCurl     = function_exists('curl_init');
        $this->useCurl     = $this->hasCurl ? (null !== $useCurl ? $useCurl : true) : false;
        $this->type        = $type;
        $this->HTTPVersion = '1.1';
        $this->host        = $host ? $host : $_SERVER['HTTP_HOST'];
        $this->uri         = $uri;
        $this->port        = $port;
        $this->timeout     = $timeout;
        $this->setHeader('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8');
        $this->setHeader('Accept-Language', 'en-us,en;q=0.5');
        $this->setHeader('Accept-Encoding', 'deflate');
        $this->setHeader('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7');
        $this->setHeader('User-Agent', 'Mozilla/5.0 Firefox/3.6.12');
        $this->setHeader('Connection', 'close');
        return $this;
    }

    public function setHost($host)
    {
        $this->host = $host;
        return $this;
    }

    public function setRequestURI($uri)
    {
        $this->uri = $uri;
        return $this;
    }

    public function setPort($port)
    {
        $this->port = $port;
        return $this;
    }

    public function setTimeout($timeout)
    {
        $this->timeout = $timeout;
        return $this;
    }

    public function setGetData($get)
    {
        $this->query = $get;
        return $this;
    }

    public function setQueryParams($get)
    {
        $this->query = $get;
        return $this;
    }

    public function setUseCurl($use)
    {
        if ($use && $this->hasCurl) {
            $this->useCurl = true;
        } else {
            $this->useCurl = false;
        }
        return $this;
    }

    public function setType($type)
    {
        if (in_array($type, ['POST', 'GET', 'PUT', 'DELETE'])) {
            $this->type = $type;
        }
        return $this;
    }

    public function setData($data)
    {
        $data && $this->data = $data;
        return $this;
    }

    public function param($data)
    {
        if (is_string($data)) {
            return $data;
        }

        $data_array = [];
        foreach ($data as $key => $val) {
            if (!is_string($val)) {
                $val = json_encode($val);
            }
            $data_array[] = urlencode($key) . '=' . urlencode($val);
        }
        return implode('&', $data_array);
    }
    public function paramJson($data)
    {
        if (is_string($data)) {
            return $data;
        }

        return json_encode($data);
    }
    public function setUrl($url)
    {
        if (!$url) {
            return $this;
        }

        $this->url                                         = $url;
        $url_info                                          = parse_url($url);
        !isset($url_info['scheme']) && $url_info['scheme'] = 'http';
        $this->port                                        = strtolower($url_info['scheme']) == 'https' ? 443 : (isset($url_info['port']) ? $url_info['port'] : 80);
        $this->host                                        = $url_info['host'];
        $this->uri                                         = $url_info['path'];
        isset($url_info['query']) && $this->setGetData($url_info['query']);
        return $this;
    }

    public function setHeader($header, $content)
    {
        $this->headers[$header] = $content;
        return $this;
    }

    public function setReferer($referer)
    {
        $this->referer = $referer;
        return $this;
    }

    public function setAdditionalCurlOpt($option, $value)
    {
        if (is_array($option)) {
            foreach ($option as $opt => $val) {
                $this->setAdditionalCurlOpt($opt, $val);
            }
        } else {
            $this->additionalCurlOpts[$option] = $value;
        }
    }

    public function setUseBasicAuth($set, $username = null, $password = null)
    {
        $this->useBasicAuth = $set;
        if ($username) {
            $this->setAuthUsername($username);
        }
        if ($password) {
            $this->setAuthPassword($password);
        }
    }

    public function setAuthUsername($username = null)
    {
        $this->authUsername = $username;
    }

    public function setAuthPassword($password = null)
    {
        $this->authPassword = $password;
    }

    public function execute()
    {
        if ($this->useCurl) {
            $this->curlExecute();
        } else {
            $this->fsockgetExecute();
        }
        return $this;
    }

    protected function curlExecute()
    {
        $uri  = $this->uri;
        $host = $this->host;
        $type = $this->type;
        $port = $this->port;
        $data = property_exists($this, 'data') ? $this->data : false;

        $timeout = $this->timeout;

        $ch = curl_init();
        // Set request type.
        if ('GET' === $type) {
            $this->setGetData($data);
            curl_setopt($ch, CURLOPT_HTTPGET, true);
        } elseif ('POST' === $type) {
            curl_setopt($ch, CURLOPT_POST, true);
            if ($data) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            }
        } elseif ('PUT' === $type) {
            curl_setopt($ch, CURLOPT_PUT, true);
        } else {
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $type);
        }
        // Grab query string.
        $query = property_exists($this, 'query') && $this->query ? '?' . self::param($this->query) : '';
//         echo $query;exit;
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        // Set additional headers.
        $headers = [];
        foreach ($this->headers as $name => $val) {
            $headers[] = $name . ': ' . $val;
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        // Do stuff it it's HTTPS/SSL.
        if (443 == $port) {
            $protocol = 'https';
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        } else {
            $protocol = 'http';
        }

        if (!empty($this->additonalCurlOpts)) {
            foreach ($this->additionalCurlOpts as $option => $value) {
                curl_setopt($ch, $option, $value);
            }
        }
        // Build and set URL.
        $url = $protocol . '://' . $host . $uri . $query;
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_PORT, $port);

        // Add any authentication to the request.
        // Currently supports only HTTP Basic Auth.
        if (true === $this->useBasicAuth) {
            curl_setopt($ch, CURLOPT_USERPWD, $this->authUsername . ':' . $this->authPassword);
            curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
        }
        if ($this->referer) {
            curl_setopt($ch, CURLOPT_REFERER, $this->referer);
        }
        //设置超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        // Execute!
        $rsp            = curl_exec($ch);
        $this->curl     = $ch;
        $this->executed = true;
        // Handle an error.
        if (!$error = curl_error($ch)) {
            $this->response        = ['responseText' => $rsp] + curl_getinfo($ch);
            $this->responseHeaders = curl_getinfo($ch);
            $this->responseText    = $rsp;
        } else {
            $this->error = $error;
        }
    }

    protected function fsockgetExecute()
    {
        $uri         = $this->uri;
        $host        = $this->host;
        $port        = $this->port;
        $type        = $this->type;
        $HTTPVersion = $this->HTTPVersion;
        $data        = property_exists($this, 'data') ? $this->data : null;
        $crlf        = "\r\n";

        $rsp = '';
//                var_dump($data);exit;
        // Deal with the data first.
        if ($data && 'POST' === $type) {
            $data = $this->param($data);
        } elseif ($data && 'GET' === $type) {
            $get_data = $data;
            $data     = $crlf;
        } else {
            $data = $crlf;
        }
        // Then add
        if ('POST' === $type) {
            if (!$this->headers['Content-Type']) {
                $this->setHeader('Content-Type', 'application/x-www-form-urlencoded');
            }
            $this->setHeader('Content-Length', strlen($data));
            $get_data = property_exists($this, 'query') && $this->query ? self::param($this->query) : false;
        } else {
            if (!$this->headers['Content-Type']) {
                $this->setHeader('Content-Type', 'text/plain');
            }
            $this->setHeader('Content-Length', strlen($crlf));
        }
        if ('GET' === $type) {
            if (isset($get_data)) {
                $get_data = $data;
            } elseif ($this->query) {
                $get_data = self::param($this->query);
            }
        }
        if (true === $this->useBasicAuth) {
            $this->setHeader('Authorization', 'Basic ' . base64_encode($this->authUsername . ':' . $this->authPassword));
        }
//         $headers = $this -> headers;
        $req = '';
        $req .= $type . ' ' . $uri . (isset($get_data) ? '?' . $get_data : '') . ' HTTP/' . $HTTPVersion . $crlf;
        $req .= 'Host: ' . $host . $crlf;
        foreach ($this->headers as $header => $content) {
            $req .= $header . ': ' . $content . $crlf;
        }
        $req .= $crlf;
        if ('POST' === $type) {
            $req .= $data;
        } else {
            $req .= $crlf;
        }

        // Construct hostname.
        $fsock_host = (443 == $port ? 'ssl://' : '') . $host;
        // Open socket.
        $httpreq = @fsockopen($fsock_host, $port, $errno, $errstr, 30);

        // Handle an error.
        if (!$httpreq) {
            $this->error = $errno . ': ' . $errstr;
            return false;
        }

        // Send the request.
        fputs($httpreq, $req);

        // Receive the response.
        while ($line = fgets($httpreq)) {
            $rsp .= $line;
        }
//            while (!feof($httpreq))
        //             {
        //                 $rsp .=@fgets($httpreq,4096);
        //             }

//         print_r($rsp);exit;
        // Extract the headers and the responseText.
        list($headers, $responseText) = explode($crlf . $crlf, $rsp);

        // Store the finalized response.
        $this->response     = $rsp;
        $this->responseText = $responseText;
//         $this -> status = array_shift($headers);
        // Store the response headers.
        $headers               = explode($crlf, $headers);
        $this->responseHeaders = [];
        foreach ($headers as $header) {
            list($key, $val)             = explode(': ', $header);
            $this->responseHeaders[$key] = $val;
        }
        // Mark as executed.
        $this->executed = true;

        // Store the resource so we can close it later.
        $this->fsock = $httpreq;
    }

    public function close()
    {
        if (!$this->executed) {
            return false;
        }
        if ($this->useCurl) {
            $this->curlClose();
        } else {
            $this->fsockgetClose();
        }
    }

    protected function fsockgetClose()
    {
        fclose($this->fsock);
    }

    protected function curlClose()
    {
        curlClose($this->curl);
    }

    public function getError()
    {
        return $this->error;
    }

    public function getResponse()
    {
        if (!$this->executed) {
            return false;
        }
        return $this->response;
    }

    public function getResponseText()
    {
        if (!$this->executed) {
            return false;
        }
        return $this->responseText;
    }

    public function getAllResponseHeaders()
    {
        if (!$this->executed) {
            return false;
        }
        return $this->responseHeaders;
    }

    public function getResponseHeader($header)
    {
        if (!$this->executed) {
            return false;
        }
        $headers = $this->responseHeaders;
        if (array_key_exists($header, $headers)) {
            return $headers[$header];
        }
    }
    public static function curlHeaders()
    {
        return [
            'User-Agent' => CURLOPT_USERAGENT,
        ];
    }
    /**
     * 发送请求方法
     * @param string $data 发送的数据 数组
     * @param string $datatype 数据格式
     * @param bool $https
     * @author baoding
     * @return array
     * @since 2016-03-09
     */
    public function sendRequest($data = '', $datatype = 'string', $https = false)
    {
        if ('json' == $datatype) {
            $this->setHeader('Content-Type', 'application/json');
            $data = $this->paramJson($data);
            $this->setData($data);
        } else {
            $data = $this->param($data);
            $this->setData($data);
        }
        //如果用了curl函数
        if ($this->useCurl) {
            Yii::info('支付接口开始执行时间' . date('Y-m-d H:i:s', time()), 'api_inter_slow');
            $this->curlExecute();
            $result = $this->getResponse();
            Yii::info('支付结束执行时间' . date('Y-m-d H:i:s', time()), 'api_inter_slow');
            $http_code = $result['http_code'];
            $http_resp = '';
            //如果接口请求时间超过10秒  计入慢日志
            if ($result['total_time'] >= 10) {
                $log = "慢api({$result['total_time']})请求的url为:" . $result['url'] . ',请求方式为:' . $this->type . ',请求的数据为:' . var_export($this->data, true);
                Yii::info($log, 'api_inter_slow');
            }
            //记录api请求错误信息
            if (200 != $http_code) {
                Yii::info(json_encode($result), 'api_inter_error');
                if (504 == $http_code) {
                    $http_resp = '请求超时';
                } else {
                    $http_resp = '请求失败';
                }
            } else {
                $http_resp = $result['responseText'];
            }
        } else {
            /*
             * 目前先临时处理
             */
            $this->fsockgetExecute();
            $result    = $this->getResponseText();
            $pregMatch = preg_match('/(\{.*?\})/is', $result, $match);
            $http_code = 200;
            $http_resp = $match[0];
        }
        $return = [
            'httpCode' => $http_code,
            'response' => $http_resp,
        ];
        return $return;
    }
}
